package com.iwhalecloud.uncc.http;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class HttpPoolClient {

	public static final String CHARSET_UTF8 = "UTF-8";
	private CloseableHttpClient httpClient;
	private Integer maxTotal = 200;
	private Integer defaultMaxPerRoute = 100;
	private Integer connectTimeout = 60000;
	private Integer connectionRequestTimeout = 500;
	private Integer socketTimeout = 15000;
	private Integer validateAfterInactivity = 2000;

	public HttpPoolClient() {
		Registry<ConnectionSocketFactory> registry = null;
		try {
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
				// 信任所有
				@Override
				public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					return true;
				}
			}).build();

			registry = RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.getSocketFactory())
					.register("https", new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
						@Override
						public boolean verify(String s, SSLSession sslSession) {
							return true;
						}
					})).build();
		} catch (Exception e) {
			log.error(ExceptionUtils.getStackTrace(e));
			registry = RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.getSocketFactory()).build();
		}
		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
		connectionManager.setMaxTotal(maxTotal);
		connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
		connectionManager.setValidateAfterInactivity(validateAfterInactivity);

		RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
				.setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();

		httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
				.setConnectionManager(connectionManager).build();
	}

	public HttpPoolClient(int maxTotal, int defaultMaxPerRoute, int connectionRequestTimeout, int connectTimeout,
			int socketTimeout, int validateAfterInactivity) {
		Registry<ConnectionSocketFactory> registry = null;
		try {
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
				// 信任所有
				@Override
				public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					return true;
				}
			}).build();

			registry = RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.getSocketFactory())
					.register("https", new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
						@Override
						public boolean verify(String s, SSLSession sslSession) {
							return true;
						}
					})).build();
		} catch (Exception e) {
			log.error(ExceptionUtils.getStackTrace(e));
			registry = RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.getSocketFactory()).build();
		}
		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
		connectionManager.setMaxTotal(maxTotal);
		connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
		connectionManager.setValidateAfterInactivity(validateAfterInactivity);

		RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
				.setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();

		httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
				.setConnectionManager(connectionManager).build();
	}

	/**
	 * 使用代理的httpClient
	 * 
	 * @param maxTotal
	 * @param defaultMaxPerRoute
	 * @param connectionRequestTimeout
	 * @param connectTimeout
	 * @param socketTimeout
	 * @param validateAfterInactivity
	 * @param netProxy
	 */
	public HttpPoolClient(int maxTotal, int defaultMaxPerRoute, int connectionRequestTimeout, int connectTimeout,
			int socketTimeout, int validateAfterInactivity, InetSocketAddress netProxy) {
		Registry<ConnectionSocketFactory> registry = null;
		try {
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
				// 信任所有
				@Override
				public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					return true;
				}
			}).build();

			registry = RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", new PlainConnectionSocketFactory() {
						@Override
						public Socket createSocket(final HttpContext context) throws IOException {
							Proxy proxy = new Proxy(Proxy.Type.SOCKS, netProxy);
							return new Socket(proxy);
						}

						@Override
						public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host,
								InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context)
								throws IOException {
							// Convert address to unresolved
							InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(host.getHostName(),
									remoteAddress.getPort());
							return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress,
									context);
						}
					}).register("https", new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
						@Override
						public boolean verify(String s, SSLSession sslSession) {
							return true;
						}
					}) {
						@Override
						public Socket createSocket(final HttpContext context) throws IOException {
							Proxy proxy = new Proxy(Proxy.Type.SOCKS, netProxy);
							return new Socket(proxy);
						}

						@Override
						public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host,
								InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context)
								throws IOException {
							// Convert address to unresolved
							InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(host.getHostName(),
									remoteAddress.getPort());
							return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress,
									context);
						}
					}).build();
		} catch (Exception e) {
			log.error(ExceptionUtils.getStackTrace(e));
			registry = RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.getSocketFactory()).build();
		}
		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry,
				new DnsResolver() {
					@Override
					public InetAddress[] resolve(String host) throws UnknownHostException {
						// Return some fake DNS record for every request, we won't be using it
						return new InetAddress[] { InetAddress.getByAddress(new byte[] { 1, 1, 1, 1 }) };
					}
				});
		connectionManager.setMaxTotal(maxTotal);
		connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
		connectionManager.setValidateAfterInactivity(validateAfterInactivity);

		RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout)
				.setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();

		httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
				.setConnectionManager(connectionManager).build();
	}

	public HttpResponse doPost(String url, String content, Map<String, String> headers) throws Exception {
		CloseableHttpResponse response = null;
		HttpResponse requestResult = new HttpResponse();

		try {
			HttpPost httpPost = new HttpPost(url);

			if (headers != null) {
				for (Map.Entry<String, String> entry : headers.entrySet()) {
					httpPost.setHeader(entry.getKey(), entry.getValue());
				}
			}
			if (content != null) {
				StringEntity contentEntity = new StringEntity(content, CHARSET_UTF8);
				httpPost.setEntity(contentEntity);
			}

			response = httpClient.execute(httpPost);
			int statusCode = response.getStatusLine().getStatusCode();

			Map<String, String> responseHeaderMap = new HashMap<String, String>();
			Header[] responseHeader = response.getAllHeaders();
			if (responseHeader != null && responseHeader.length > 0) {
				for (int i = 0; i < responseHeader.length; i++) {
					responseHeaderMap.put(responseHeader[i].getName(), responseHeader[i].getValue());
				}
			}

			HttpEntity httpEntity = response.getEntity();
			String result = null;
			if (httpEntity != null) {
				result = EntityUtils.toString(httpEntity, CHARSET_UTF8);
			}

			requestResult.setContent(result);
			requestResult.setCode(statusCode);
			Map<String, String> rspHeaders = new HashMap<>();
			for (Header header : response.getAllHeaders()) {
				rspHeaders.put(header.getName(), header.getValue());
			}
			requestResult.setHeaders(rspHeaders);

			EntityUtils.consume(httpEntity);
			return requestResult;
		} catch (Exception ex) {
			throw ex;
		} finally {
			if (response != null) {
				response.close();
			}
		}
	}

	public HttpResponse doGet(String url, Map<String, String> headers) throws Exception {
		CloseableHttpResponse response = null;
		HttpResponse requestResult = new HttpResponse();

		try {
			HttpGet httpGet = new HttpGet(url);
			if (headers != null) {
				for (Map.Entry<String, String> entry : headers.entrySet()) {
					httpGet.setHeader(entry.getKey(), entry.getValue());
				}
			}

			response = httpClient.execute(httpGet);

			int statusCode = response.getStatusLine().getStatusCode();

			Map<String, String> responseHeaderMap = new HashMap<String, String>();
			Header[] responseHeader = response.getAllHeaders();
			if (responseHeader != null && responseHeader.length > 0) {
				for (int i = 0; i < responseHeader.length; i++) {
					responseHeaderMap.put(responseHeader[i].getName(), responseHeader[i].getValue());
				}
			}

			HttpEntity httpEntity = response.getEntity();
			String result = null;
			if (httpEntity != null) {
				result = EntityUtils.toString(httpEntity, CHARSET_UTF8);
			}

			requestResult.setContent(result);
			requestResult.setCode(statusCode);
			Map<String, String> rspHeaders = new HashMap<>();
			for (Header header : response.getAllHeaders()) {
				rspHeaders.put(header.getName(), header.getValue());
			}
			requestResult.setHeaders(rspHeaders);

			EntityUtils.consume(httpEntity);
			return requestResult;
		} catch (Exception ex) {
			throw ex;
		} finally {
			if (response != null) {
				response.close();
			}
		}
	}

	public HttpResponse doPut(String url, String content, Map<String, String> headers) throws Exception {
		CloseableHttpResponse response = null;
		HttpResponse requestResult = new HttpResponse();

		try {
			HttpPut httpPut = new HttpPut(url);

			if (headers != null) {
				for (Map.Entry<String, String> entry : headers.entrySet()) {
					httpPut.setHeader(entry.getKey(), entry.getValue());
				}
			}
			if (content != null) {
				StringEntity contentEntity = new StringEntity(content, CHARSET_UTF8);
				httpPut.setEntity(contentEntity);
			}

			response = httpClient.execute(httpPut);

			int statusCode = response.getStatusLine().getStatusCode();

			Map<String, String> responseHeaderMap = new HashMap<String, String>();
			Header[] responseHeader = response.getAllHeaders();
			if (responseHeader != null && responseHeader.length > 0) {
				for (int i = 0; i < responseHeader.length; i++) {
					responseHeaderMap.put(responseHeader[i].getName(), responseHeader[i].getValue());
				}
			}

			HttpEntity httpEntity = response.getEntity();
			String result = null;
			if (httpEntity != null) {
				result = EntityUtils.toString(httpEntity, CHARSET_UTF8);
			}

			requestResult.setContent(result);
			requestResult.setCode(statusCode);
			Map<String, String> rspHeaders = new HashMap<>();
			for (Header header : response.getAllHeaders()) {
				rspHeaders.put(header.getName(), header.getValue());
			}
			requestResult.setHeaders(rspHeaders);

			EntityUtils.consume(httpEntity);
			return requestResult;
		} catch (Exception ex) {
			throw ex;
		} finally {
			if (response != null) {
				response.close();
			}
		}
	}

	public void download(String url, File localFile, Map<String, String> headers) throws Exception {
		CloseableHttpResponse response = null;
		FileOutputStream fileout = null;
		try {
			log.info("url:" + url);
			HttpGet httpGet = new HttpGet(url);
			if (headers != null) {
				for (Map.Entry<String, String> entry : headers.entrySet()) {
					httpGet.setHeader(entry.getKey(), entry.getValue());
				}
			}
			response = httpClient.execute(httpGet);
			int statusCode = response.getStatusLine().getStatusCode();

			if (statusCode == 200) {
				HttpEntity entity = response.getEntity();
				InputStream is = entity.getContent();

				fileout = new FileOutputStream(localFile);
				byte[] buffer = new byte[10 * 1024];
				int ch = 0;
				while ((ch = is.read(buffer)) != -1) {
					fileout.write(buffer, 0, ch);
				}
				is.close();
				fileout.flush();
			}
		} catch (Exception ex) {
			throw ex;
		} finally {
			if (response != null) {
				response.close();
			}
			if (fileout != null) {
				fileout.close();
			}
		}
	}
}
